﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using UnityEngine;
using UnityEngine.Assertions;
public struct ArrayRange
{
    public int startIndex;
    public int endIndex;
}
public struct JobExcuteProgress
{
    public float perCent;
    public int totalCount;
    public int finishCount;

    public int[] workerJobList;
}
/// <summary>
/// 一个勉强能用且性能不高的多线程工具
/// 
/// *注意线程数量和Job数量的控制,目前还是Dequeue有锁的,会优化
/// *对于迭代多次但是每次迭代的计算不复杂的计算,建议使用 :DoParallelFor<T>
/// 
/// </summary>
public static class ParallelTask
{
    public static volatile bool isWorkingNow = false;
    public static volatile int totalJobCount = 0;

    public static volatile List<TheadedWorker> workers = new List<TheadedWorker>();
    public static volatile Queue<BaseJob> g_Jobs = new Queue<BaseJob>();
    public static volatile object g_JobsLocker = new object();
    private static DateTime before;
    public static void Clear(int count)
    {
        ClearJobs();
        if (workers.Count != count)
        {
            workers.Clear();
            count = Mathf.Max(1, count);
            for (int i = 0; i < count; i++)
            {
                workers.Add(new TheadedWorker());
            }
        }
        else
        {
            Debug.Log("ResetWorker");
            for (int i = 0; i < count; i++)
            {
                workers[i].Reset();
            }
        }
    }
    public static void ClearJobs()
    {
        totalJobCount = 0;

        lock (g_JobsLocker)
        {
            if (g_Jobs.Count == 0)
            {
                return;
            }
            foreach (var item in g_Jobs)
            {
                item.Release();
            }
            g_Jobs.Clear();
        }
    }
    public static void WaitForWorkerDone()
    {
        foreach (var item in workers)
        {
            item.WaitDone();
        }
    }
    public static void BeginParallelTask(int core = 6, Action<JobExcuteProgress> updateFunc = null)
    {
        Assert.IsFalse(isWorkingNow);

        isWorkingNow = true;

        before = DateTime.Now;
        Clear(core);
        Assert.AreEqual(totalJobCount, 0);
        foreach (var worker in workers)
        {
            worker.SetJobFetch(g_Jobs);
            worker.StartWorkerLoop();
        }
        Debug.Log("BeginParallelTask cost: " + (DateTime.Now - before).Ticks);

        if (updateFunc != null)
        {
            UpdateLoop(updateFunc);
        }
    }


    public static void UpdateLoop(Action<JobExcuteProgress> updateFunc)
    {

        ThreadPool.QueueUserWorkItem((arg) =>
        {
            Action<JobExcuteProgress> callBack = (Action<JobExcuteProgress>)arg;

            while (isWorkingNow)
            {
                int _totalJobCount = totalJobCount;
                int finishCount = 0;
                int[] workerList = new int[workers.Count];
                int temp = 0;
                foreach (var worker in workers)
                {
                    int count = worker.GetFinishJobCount();
                    finishCount += count;
                    workerList[temp] = count;
                    temp++;
                }
                float perCent = (float)finishCount / Mathf.Max(1, _totalJobCount);
                if (updateFunc != null)
                {
                    callBack(new JobExcuteProgress() { finishCount = finishCount, perCent = perCent, totalCount = _totalJobCount, workerJobList = workerList });
                }
            }
            Debug.Log("UpdateThreadQuit");
        }, updateFunc);
    }
    public static void DoParallelFor<T>(T[] collection, Action<T, int> action, int splitCount = 8)
    {
        Assert.IsTrue(isWorkingNow);
        ArrayRange[] ranges = new ArrayRange[splitCount];
        int step = collection.Length / splitCount;
        for (int i = 0; i < splitCount; i++)
        {
            ranges[i] = new ArrayRange();
            ranges[i].startIndex = i * step;
            if (i == splitCount - 1)
            {
                ranges[i].endIndex = collection.Length - ranges[i].startIndex;
            }
            else
            {
                ranges[i].endIndex = (i + 1) * step;
            }
        }
        for (int i = 0; i < splitCount; i++)
        {
            DoParallelTask((r) =>
            {
                ArrayRange curR = (ArrayRange)r;
                for (int index = curR.startIndex; index < curR.endIndex; index++)
                {
                    action(collection[index], index);
                }
            }, ranges[i]);
        }

    }
    public static void DoParallelFor(int length, Action<int> action, int splitCount = 6)
    {
        Assert.IsTrue(isWorkingNow);

        if (splitCount < 0)
        {
            Debug.LogError("Split Count Must > 0");
            EndParallelTask();
            return;
        }
        ArrayRange[] ranges = new ArrayRange[splitCount];
        int step = length / splitCount;
        for (int i = 0; i < splitCount; i++)
        {
            ranges[i] = new ArrayRange();
            ranges[i].startIndex = i * step;
            if (i == splitCount - 1)
            {
                ranges[i].endIndex = length;
            }
            else
            {
                ranges[i].endIndex = (i + 1) * step;
            }
        }
        for (int i = 0; i < splitCount; i++)
        {

            DoParallelTask((r) =>
            {
                ArrayRange curR = (ArrayRange)r;
                for (int index = curR.startIndex; index < curR.endIndex; index++)
                {
                    action(index);
                }
            }, ranges[i]);
        }

    }
    public static void DoParallelTask(Action<object> func, object args = null)
    {
        Assert.IsTrue(isWorkingNow);

        var theJob = new BaseJob();
        theJob.func = func;
        theJob.args = args;
        lock (g_JobsLocker)
        {
            g_Jobs.Enqueue(theJob);
        }
        totalJobCount++;
    }
    public static void EndParallelTask()
    {
        Assert.IsTrue(isWorkingNow);
        Debug.Log("至完成分配任务消耗 : " + (DateTime.Now - before).Ticks);
        WaitForWorkerDone();
        isWorkingNow = false;
        Debug.Log("此次任务总共消耗 : " + (DateTime.Now - before).TotalSeconds + " (s)");
    }
}

